package eu.expressis.eiaf.core.infrastructure.integration.db.dao;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceException;

import org.springframework.orm.jpa.JpaCallback;
import org.springframework.orm.jpa.support.JpaDaoSupport;

import eu.expressis.eiaf.core.infrastructure.integration.db.entity.AbstractEntity;

public abstract class AbstractDefaultJpaDao<E extends AbstractEntity> extends JpaDaoSupport implements DefaultDao<E> {

  // volatile because of double checked locking in getEntityClass()
  @SuppressWarnings("unchecked")
  private volatile Class entityClass;

  /**
   * @see eu.europa.ec.agri.jcore.persistence.common.DefaultDao#getTotalCount()
   */
  public long getTotalCount() {
    return (Long) getJpaTemplate().execute(new JpaCallback() {
      public Object doInJpa(EntityManager em) throws PersistenceException {
        return em.createQuery("SELECT COUNT(*) " + "FROM " + getEntityClass().getSimpleName()).getSingleResult();
      }
    });
  }

  /**
   * @see eu.europa.ec.agri.jcore.persistence.common.DefaultDao#findAllEntities()
   */
  @SuppressWarnings("unchecked")
  public Collection<E> findAllEntities() {
    return getJpaTemplate().find("FROM " + getEntityClass().getName());
  }

  /**
   * @see eu.europa.ec.agri.jcore.persistence.common.DefaultDao#findByPrimaryKey(java.io.Serializable)
   */
  @SuppressWarnings("unchecked")
  public E findByPrimaryKey(Serializable pk) {
    return (E) getJpaTemplate().find(getEntityClass(), pk);
  }

  /**
   * @see eu.europa.ec.agri.jcore.persistence.common.DefaultDao#remove(eu.europa.ec.agri.jcore.persistence.common.AbstractPersistentEntity)
   */
  public void remove(E entity) {
    getJpaTemplate().remove(entity);
  }

  /**
   * @see eu.europa.ec.agri.jcore.persistence.common.DefaultDao#removeByPrimaryKey(java.io.Serializable)
   */
  public void removeByPrimaryKey(Serializable pk) {
    E entity = findByPrimaryKey(pk);
    remove(entity);
  }

  /**
   * @see eu.europa.ec.agri.jcore.persistence.common.DefaultDao#persist(eu.europa.ec.agri.jcore.persistence.common.AbstractPersistentEntity)
   */
  public E persist(E entity) {
    getJpaTemplate().persist(entity);
    return entity;
  }

  /**
   * @see eu.europa.ec.agri.jcore.persistence.common.DefaultDao#merge(eu.europa.ec.agri.jcore.persistence.common.AbstractPersistentEntity)
   */
  public E merge(E entity) {
    return getJpaTemplate().merge(entity);
  }

  /**
   * Dynamically detect the entity class.
   * 
   * @return the class of this entity
   */
  @SuppressWarnings("unchecked")
  protected Class getEntityClass() {

    if (entityClass == null) {
      synchronized (this) {
        if (entityClass == null) {
          // get our parent class
          Type type = getClass().getGenericSuperclass();
          loop: while (true) {
            if (type instanceof ParameterizedType) {
              Type[] arguments = ((ParameterizedType) type).getActualTypeArguments();
              for(Type argument : arguments) {
                if (argument instanceof Class && AbstractEntity.class.isAssignableFrom(((Class) argument))) {
                  entityClass = (Class) argument;
                  break loop;
                }
              }
            }
            type = ((Class) type).getGenericSuperclass();
            if (type == Object.class) {
              throw new RuntimeException("The entity class does not extend " + AbstractEntity.class.getName());
            }
          }
        }
      }
    }
    return entityClass;
  }
}
